package sm.upgrade.util;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sm.dam.api.DDLAdaptor;
import sm.dam.api.DDLAdaptorFactory;
import sm.dbdict.DBDictChangeUtil;
import sm.dbdict.DBDictUtil;
import sm.dbdict.def.DBDict;
import sm.dbdict.def.Field;
import sm.dbdict.def.Key;
import sm.dbdict.def.SqlFieldOptions;
import sm.dbdict.def.SqlIndex;
import sm.dbdict.def.SqlTables;
import sm.def.KeyType;
import sm.upgrade.api.UpgradePhase;
import sm.util.SMMap;
import sm.util.Util;

public class UpgradeUtil {

	private static Logger logger = LoggerFactory.getLogger(UpgradeUtil.class);

	public static SMMap createUpgradeResult(String fileName,
			Object[] keyValues, UpgradeResult result) {
		SMMap upgradeResult = new SMMap();
		upgradeResult.put("object_type", fileName);
		upgradeResult.put("object_name", Util.formatKey(keyValues));
		upgradeResult.put("result", result.getName());
		upgradeResult.put("sysmodcount", 0);
		upgradeResult.put("sysmoduser", UpgradePhase.UPGRADE_USER);
		upgradeResult.put("sysmodtime", new Date());
		return upgradeResult;
	}

	private static SqlFieldOptions mapSqlFieldOptions(
			SqlFieldOptions upgradeSqlFieldOptions,
			Map<String, String> aliasMapping) {
		SqlFieldOptions newSqlFieldOptions = null;
		if (upgradeSqlFieldOptions != null) {
			newSqlFieldOptions = (SqlFieldOptions) upgradeSqlFieldOptions
					.clone();
			String upgradeAlias = upgradeSqlFieldOptions.getSql_table_alias();
			String customerAlias = aliasMapping.get(upgradeAlias);
			newSqlFieldOptions.setSql_table_alias(customerAlias);
		}
		return newSqlFieldOptions;
	}

	private static Field mapField(Field upgradeField,
			Map<String, String> aliasMapping) {
		Field newField = (Field) upgradeField.clone();
		newField.setSql_field_options(mapSqlFieldOptions(
				upgradeField.getSql_field_options(), aliasMapping));
		return newField;
	}

	public static DBDict upgradeDBDict(DBDict customerDBDict,
			DBDict upgradeDBDict) {
		DBDictUtil.refineDBDict(upgradeDBDict);

		DBDict newDBDict = (DBDict) customerDBDict.clone();
		// map tables and add new tables
		Map<String, String> nameMapping = new HashMap<String, String>();
		Map<String, String> aliasMapping = new HashMap<String, String>();

		String dbType = upgradeDBDict.getSql_tables().get(0).getSql_db_type();
		DDLAdaptor ddlAdaptor = DDLAdaptorFactory.getDDLAdaptor(dbType);
		for (SqlTables upgradeTable : upgradeDBDict.getSql_tables()) {

			String upgradeName = upgradeTable.getSql_table_name();
			String customerName = upgradeName;
			String upgradeAlias = upgradeTable.getSql_table_alias();
			String customerAlias = upgradeAlias;
			SqlTables customerTable = DBDictChangeUtil.getMatchedTable(
					upgradeTable, upgradeDBDict, newDBDict);
			if (customerTable == null) { // table not exist
				SqlTables newTable = (SqlTables) upgradeTable.clone();

				SqlTables sameAliasTable = DBDictUtil.getTableByAlias(
						newDBDict.getSql_tables(), upgradeAlias);
				if (sameAliasTable != null) {
					customerAlias = upgradeAlias + "_renamed";
				}
				newTable.setSql_table_alias(customerAlias);

				SqlTables sameNameTable = DBDictUtil.getTableByName(
						newDBDict.getSql_tables(), upgradeName);
				if (sameNameTable != null) {
					customerName = upgradeName + "_renamed";
				}
				newTable.setSql_table_name(customerName);

				newDBDict.getSql_tables().add(newTable);
			} else {
				customerName = customerTable.getSql_table_name();
				customerAlias = customerTable.getSql_table_alias();
			}
			aliasMapping.put(upgradeAlias, customerAlias);
			nameMapping.put(upgradeName, customerName);
		}

		// update field
		List<Field> upgradeFields = upgradeDBDict.getField();
		for (int i = 0; i < upgradeFields.size(); i++) {
			Field upgradeField = upgradeFields.get(i);
			SqlFieldOptions upgradeSqlFieldOptions = upgradeField
					.getSql_field_options();
			String fieldPath = DBDictUtil.getFieldPath(upgradeField);
			Field customerField = DBDictUtil.getField(fieldPath,
					newDBDict.getField());
			if (customerField == null) { // add new field with its children
				Field newField = mapField(upgradeField, aliasMapping);
				List<Field> upgradeSubFields = DBDictUtil.getSubFields(
						upgradeFields, upgradeField);
				List<Field> newSubFields = new ArrayList<Field>(
						upgradeSubFields.size());
				for (Field upgradeSubField : upgradeSubFields) {
					Field newSubField = mapField(upgradeSubField, aliasMapping);
					newSubFields.add(newSubField);
				}

				DBDictUtil.insertField(newDBDict, newField, newSubFields);
				i += newSubFields.size();
			} else { // update current field
				SqlFieldOptions newSqlFieldOptions = mapSqlFieldOptions(
						upgradeSqlFieldOptions, aliasMapping);

				SqlFieldOptions customerSqlFieldOptions = customerField
						.getSql_field_options();
				// use the old sql name if both are db columns
				String upgradeColumnName = upgradeSqlFieldOptions != null ? upgradeSqlFieldOptions
						.getSql_column_name() : null;
				String customerColumnName = customerSqlFieldOptions != null ? customerSqlFieldOptions
						.getSql_column_name() : null;
				if (customerSqlFieldOptions != null
						&& newSqlFieldOptions != null
						&& upgradeColumnName != null
						&& customerColumnName != null) {
					newSqlFieldOptions
							.setSql_column_name(customerSqlFieldOptions
									.getSql_column_name());
				}
				int upgradeFieldType = upgradeField.getType();
				int customerFieldType = customerField.getType();
				if (upgradeFieldType != customerFieldType) {
					customerField.setType(upgradeFieldType);
					customerField.setSql_field_options(newSqlFieldOptions);
				} else if (DBDictUtil.isDBField(customerField)
						|| DBDictUtil.isDBField(upgradeField)) {
					Boolean upgradeRC = upgradeSqlFieldOptions != null ? upgradeSqlFieldOptions
							.getSql_rc_format() : false;
					Boolean customerRC = customerSqlFieldOptions != null ? customerSqlFieldOptions
							.getSql_rc_format() : false;
					String upgradeAlias = newSqlFieldOptions != null ? newSqlFieldOptions
							.getSql_table_alias() : null;
					String customerAlias = customerSqlFieldOptions != null ? customerSqlFieldOptions
							.getSql_table_alias() : null;
					String upgradeSqlType = upgradeSqlFieldOptions != null ? upgradeSqlFieldOptions
							.getSql_data_type() : null;
					String customerSqlType = customerSqlFieldOptions != null ? customerSqlFieldOptions
							.getSql_data_type() : null;
					if (customerRC != upgradeRC) {
						customerField.setSql_field_options(newSqlFieldOptions);

					} else if (!Util.equals(customerAlias, upgradeAlias)
							|| !Util.equals(customerSqlType, upgradeSqlType)) {
						if (newSqlFieldOptions != null) {
							if (ddlAdaptor.isSameType(customerSqlType,
									upgradeSqlType)) {
								if (ddlAdaptor.isBigger(upgradeSqlType,
										customerSqlType)) {
									newSqlFieldOptions
											.setSql_data_type(upgradeSqlType);
								} else {
									newSqlFieldOptions
											.setSql_data_type(customerSqlType);
								}
							}
						}
						customerField.setSql_field_options(newSqlFieldOptions);
					} else if (!Util.equals(customerColumnName,
							upgradeColumnName)) {
						customerField.setSql_field_options(newSqlFieldOptions);
					}

				}

			}
		}

		// update keys
		List<Key> upgradeKeys = upgradeDBDict.getKey();
		List<Key> newKeys = new ArrayList<Key>();
		for (Key upgradeKey : upgradeKeys) {
			Key customerKey = null;
			int upgradeKeyFlags = upgradeKey.getFlags();
			if (KeyType.isUniqueKey(upgradeKey.getFlags())) {
				Key sameKey = DBDictChangeUtil.getSameKey(upgradeKeys,
						upgradeKey, -2, true);
				if (sameKey != null) {
					// skip duplicated unique key
					continue;
				}
				customerKey = DBDictChangeUtil.getSameKey(newDBDict.getKey(),
						upgradeKey, 3, true);
			} else {
				customerKey = DBDictChangeUtil.getSameKey(newDBDict.getKey(),
						upgradeKey, 0, false);
			}

			Key newKey = (Key) upgradeKey.clone();
			if (customerKey == null) {// add new key
				for (SqlIndex newIndex : newKey.getSqlIndexes()) {
					String upgradeTableName = newIndex.getTableName();
					String customerTableName = nameMapping
							.get(upgradeTableName);
					newIndex.setTableName(customerTableName);
				}
				newKeys.add(newKey);
				if (logger.isDebugEnabled()) {
					logger.debug("Added "
							+ KeyType.getType(upgradeKeyFlags).getName()
							+ " key " + DBDictUtil.getKeyString(newKey)
							+ " to table " + newDBDict.getName());
				}
			} else { // update current unique key
				customerKey.setFlags(newKey.getFlags());
				customerKey.setName(newKey.getName());
				List<SqlIndex> customerIndexes = new ArrayList<SqlIndex>();
				for (SqlIndex upgradeIndex : newKey.getSqlIndexes()) {
					String upgradeTableName = upgradeIndex.getTableName();
					String customerTableName = nameMapping
							.get(upgradeTableName);

					// index may have a table not exist
					if (customerTableName != null) {
						SqlIndex customerIndex = DBDictUtil.getSqlIndexes(
								customerKey, customerTableName);
						if (customerIndex == null) {
							customerIndex = (SqlIndex) upgradeIndex.clone();
							customerIndex.setTableName(customerTableName);
						} else {
							customerIndex.setType(upgradeIndex.getType());
						}
						customerIndexes.add(customerIndex);
					}
				}
				customerKey.setSqlIndexes(customerIndexes);
			}
		}
		newDBDict.getKey().addAll(0, newKeys);

		return newDBDict;

	}

}
